//! Variable extractors
//! 
//! This module implements extractors for pulling values from responses
//! and storing them as variables for use in subsequent steps.

use super::{Extractor, ValidationContext};
use crate::error::Result;
use crate::models::Value;
use regex::Regex;
use serde_json::json;

/// JSON path extractor
#[derive(Debug)]
pub struct JsonPathExtractor {
    path: String,
    variable_name: String,
}

impl JsonPathExtractor {
    pub fn new(path: String, variable_name: String) -> Self {
        Self { path, variable_name }
    }
}

impl Extractor for JsonPathExtractor {
    fn extract(&self, context: &ValidationContext) -> Result<Option<Value>> {
        Ok(context.get_response_value(&self.path).cloned())
    }
    
    fn name(&self) -> &str {
        "json_path"
    }
    
    fn variable_name(&self) -> &str {
        &self.variable_name
    }
}

/// Regex extractor for extracting values using regular expressions
#[derive(Debug)]
pub struct RegexExtractor {
    path: String,
    pattern: Regex,
    variable_name: String,
    group_index: usize,
}

impl RegexExtractor {
    pub fn new(path: String, pattern: &str, variable_name: String, group_index: Option<usize>) -> Result<Self> {
        let regex = Regex::new(pattern)
            .map_err(|e| crate::error::Error::Validation(
                crate::error::ValidationError::InvalidPattern {
                    pattern: pattern.to_string(),
                    message: e.to_string(),
                }
            ))?;
        
        Ok(Self {
            path,
            pattern: regex,
            variable_name,
            group_index: group_index.unwrap_or(0),
        })
    }
}

impl Extractor for RegexExtractor {
    fn extract(&self, context: &ValidationContext) -> Result<Option<Value>> {
        if let Some(Value::String(text)) = context.get_response_value(&self.path) {
            if let Some(captures) = self.pattern.captures(text) {
                if let Some(matched) = captures.get(self.group_index) {
                    return Ok(Some(json!(matched.as_str())));
                }
            }
        }
        Ok(None)
    }
    
    fn name(&self) -> &str {
        "regex"
    }
    
    fn variable_name(&self) -> &str {
        &self.variable_name
    }
}

/// Header extractor for extracting HTTP header values
#[derive(Debug)]
pub struct HeaderExtractor {
    header_name: String,
    variable_name: String,
}

impl HeaderExtractor {
    pub fn new(header_name: String, variable_name: String) -> Self {
        Self { header_name, variable_name }
    }
}

impl Extractor for HeaderExtractor {
    fn extract(&self, context: &ValidationContext) -> Result<Option<Value>> {
        let headers_path = "headers";
        if let Some(Value::Object(headers)) = context.get_response_value(headers_path) {
            // Try exact match first
            if let Some(value) = headers.get(&self.header_name) {
                return Ok(Some(value.clone()));
            }
            
            // Try case-insensitive match
            let header_lower = self.header_name.to_lowercase();
            for (key, value) in headers {
                if key.to_lowercase() == header_lower {
                    return Ok(Some(value.clone()));
                }
            }
        }
        Ok(None)
    }
    
    fn name(&self) -> &str {
        "header"
    }
    
    fn variable_name(&self) -> &str {
        &self.variable_name
    }
}

/// Cookie extractor for extracting cookie values
#[derive(Debug)]
pub struct CookieExtractor {
    cookie_name: String,
    variable_name: String,
}

impl CookieExtractor {
    pub fn new(cookie_name: String, variable_name: String) -> Self {
        Self { cookie_name, variable_name }
    }
}

impl Extractor for CookieExtractor {
    fn extract(&self, context: &ValidationContext) -> Result<Option<Value>> {
        let cookies_path = "cookies";
        if let Some(Value::Object(cookies)) = context.get_response_value(cookies_path) {
            if let Some(value) = cookies.get(&self.cookie_name) {
                return Ok(Some(value.clone()));
            }
        }
        
        // Also try to extract from Set-Cookie header
        if let Some(Value::String(set_cookie)) = context.get_response_value("headers.set-cookie") {
            if let Some(cookie_value) = self.extract_from_set_cookie(set_cookie) {
                return Ok(Some(json!(cookie_value)));
            }
        }
        
        Ok(None)
    }
    
    fn name(&self) -> &str {
        "cookie"
    }
    
    fn variable_name(&self) -> &str {
        &self.variable_name
    }
}

impl CookieExtractor {
    fn extract_from_set_cookie(&self, set_cookie: &str) -> Option<String> {
        // Parse Set-Cookie header format: "name=value; Path=/; HttpOnly"
        for cookie_part in set_cookie.split(',') {
            let cookie_part = cookie_part.trim();
            if let Some(eq_pos) = cookie_part.find('=') {
                let name = cookie_part[..eq_pos].trim();
                if name == self.cookie_name {
                    let value_part = &cookie_part[eq_pos + 1..];
                    if let Some(semicolon_pos) = value_part.find(';') {
                        return Some(value_part[..semicolon_pos].trim().to_string());
                    } else {
                        return Some(value_part.trim().to_string());
                    }
                }
            }
        }
        None
    }
}

/// Status code extractor
#[derive(Debug)]
pub struct StatusCodeExtractor {
    variable_name: String,
}

impl StatusCodeExtractor {
    pub fn new(variable_name: String) -> Self {
        Self { variable_name }
    }
}

impl Extractor for StatusCodeExtractor {
    fn extract(&self, context: &ValidationContext) -> Result<Option<Value>> {
        Ok(context.get_response_value("status_code").cloned())
    }
    
    fn name(&self) -> &str {
        "status_code"
    }
    
    fn variable_name(&self) -> &str {
        &self.variable_name
    }
}

/// Response time extractor
#[derive(Debug)]
pub struct ResponseTimeExtractor {
    variable_name: String,
}

impl ResponseTimeExtractor {
    pub fn new(variable_name: String) -> Self {
        Self { variable_name }
    }
}

impl Extractor for ResponseTimeExtractor {
    fn extract(&self, context: &ValidationContext) -> Result<Option<Value>> {
        // Try to get elapsed time from response data
        if let Some(elapsed) = context.get_response_value("elapsed") {
            return Ok(Some(elapsed.clone()));
        }
        
        // Try to get from http_stat
        if let Some(Value::Object(http_stat)) = context.get_response_value("http_stat") {
            if let Some(elapsed) = http_stat.get("elapsed_ms") {
                return Ok(Some(elapsed.clone()));
            }
        }
        
        Ok(None)
    }
    
    fn name(&self) -> &str {
        "response_time"
    }
    
    fn variable_name(&self) -> &str {
        &self.variable_name
    }
}

/// Content length extractor
#[derive(Debug)]
pub struct ContentLengthExtractor {
    variable_name: String,
}

impl ContentLengthExtractor {
    pub fn new(variable_name: String) -> Self {
        Self { variable_name }
    }
}

impl Extractor for ContentLengthExtractor {
    fn extract(&self, context: &ValidationContext) -> Result<Option<Value>> {
        // Try to get from headers first
        if let Some(length) = context.get_response_value("headers.content-length") {
            if let Some(length_str) = length.as_str() {
                if let Ok(length_num) = length_str.parse::<i64>() {
                    return Ok(Some(json!(length_num)));
                }
            }
        }
        
        // Try to get from response body length
        if let Some(Value::String(body)) = context.get_response_value("body") {
            return Ok(Some(json!(body.len() as i64)));
        }
        
        Ok(None)
    }
    
    fn name(&self) -> &str {
        "content_length"
    }
    
    fn variable_name(&self) -> &str {
        &self.variable_name
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::collections::HashMap;
    use serde_json::json;

    fn create_test_context() -> ValidationContext {
        let response = HashMap::from([
            ("status_code".to_string(), json!(200)),
            ("headers".to_string(), json!({
                "content-type": "application/json",
                "content-length": "42",
                "set-cookie": "session_id=abc123; Path=/; HttpOnly"
            })),
            ("body".to_string(), json!({
                "message": "Hello, World!",
                "data": {
                    "user_id": 12345,
                    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
                }
            })),
            ("elapsed".to_string(), json!(150)),
        ]);
        
        ValidationContext::new(response, HashMap::new())
    }

    #[test]
    fn test_json_path_extractor() {
        let context = create_test_context();
        
        let extractor = JsonPathExtractor::new("body.data.user_id".to_string(), "user_id".to_string());
        let result = extractor.extract(&context).unwrap();
        assert_eq!(result, Some(json!(12345)));
        assert_eq!(extractor.variable_name(), "user_id");
    }

    #[test]
    fn test_regex_extractor() {
        let context = create_test_context();
        
        let extractor = RegexExtractor::new(
            "body.data.token".to_string(),
            r"^([^.]+)",
            "token_header".to_string(),
            Some(1),
        ).unwrap();
        
        let result = extractor.extract(&context).unwrap();
        assert_eq!(result, Some(json!("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9")));
    }

    #[test]
    fn test_header_extractor() {
        let context = create_test_context();
        
        let extractor = HeaderExtractor::new("content-type".to_string(), "content_type".to_string());
        let result = extractor.extract(&context).unwrap();
        assert_eq!(result, Some(json!("application/json")));
        
        // Test case-insensitive
        let extractor = HeaderExtractor::new("Content-Type".to_string(), "content_type".to_string());
        let result = extractor.extract(&context).unwrap();
        assert_eq!(result, Some(json!("application/json")));
    }

    #[test]
    fn test_cookie_extractor() {
        let context = create_test_context();
        
        let extractor = CookieExtractor::new("session_id".to_string(), "session".to_string());
        let result = extractor.extract(&context).unwrap();
        assert_eq!(result, Some(json!("abc123")));
    }

    #[test]
    fn test_status_code_extractor() {
        let context = create_test_context();
        
        let extractor = StatusCodeExtractor::new("status".to_string());
        let result = extractor.extract(&context).unwrap();
        assert_eq!(result, Some(json!(200)));
    }

    #[test]
    fn test_response_time_extractor() {
        let context = create_test_context();
        
        let extractor = ResponseTimeExtractor::new("elapsed_time".to_string());
        let result = extractor.extract(&context).unwrap();
        assert_eq!(result, Some(json!(150)));
    }

    #[test]
    fn test_content_length_extractor() {
        let context = create_test_context();
        
        let extractor = ContentLengthExtractor::new("length".to_string());
        let result = extractor.extract(&context).unwrap();
        assert_eq!(result, Some(json!(42)));
    }
}
